﻿using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using EverythingToolbar.Helpers;
using Microsoft.Xaml.Behaviors;
using NLog;
using Point = System.Drawing.Point;

namespace EverythingToolbar.Deskband
{
    internal class SearchWindowPlacement : Behavior<SearchWindow>
    {
        private static readonly ILogger Logger = ToolbarLogger.GetLogger<SearchWindowPlacement>();

        public FrameworkElement? PlacementTarget;

        private double _dpiScalingFactor;

        protected override void OnAttached()
        {
            AssociatedObject.Showing += OnShowing;
            AssociatedObject.Hiding += OnHiding;

            if (PlacementTarget != null)
                PlacementTarget.Loaded += OnPlacementTargetLoaded;
        }

        private void OnPlacementTargetLoaded(object sender, RoutedEventArgs e)
        {
            _dpiScalingFactor = GetScalingFactor();
        }

        private void OnHiding(object? sender, EventArgs e)
        {
            AssociatedObject.AnimateHide(TaskbarStateManager.Instance.TaskbarEdge);
        }

        private void OnShowing(object? sender, EventArgs e)
        {
            var position = CalculatePosition();
            AssociatedObject.AnimateShow(
                position.Left * _dpiScalingFactor,
                position.Top * _dpiScalingFactor,
                (position.Right - position.Left) * _dpiScalingFactor,
                (position.Bottom - position.Top) * _dpiScalingFactor,
                TaskbarStateManager.Instance.TaskbarEdge
            );
        }

        private Rect CalculatePosition()
        {
            if (
                PlacementTarget == null
                || PresentationSource.FromVisual(PlacementTarget) as HwndSource is not { } hwndSource
            )
            {
                Logger.Error("Failed to get HwndSource from PlacementTarget. Cannot calculate window position.");
                return new Rect();
            }

            GetWindowRect(hwndSource.Handle, out var placementTarget);

            var placementTargetPos = new Point(placementTarget.Left, placementTarget.Top);
            var screen = Screen.FromPoint(placementTargetPos);
            var workingArea = screen.WorkingArea;
            var screenBounds = screen.Bounds;
            var windowSize = GetTargetWindowSize();
            var taskbarSize = TaskbarStateManager.Instance.TaskbarSize;
            var margin = GetMargin();

            var windowPosition = new Rect();
            switch (TaskbarStateManager.Instance.TaskbarEdge)
            {
                case Helpers.Edge.Bottom:
                case Helpers.Edge.Top:
                    // In case of auto-hiding taskbar the working area is not affected by the taskbar.
                    // Therefore the taskbar size needs to be handled separately.
                    var topDockPos = Math.Max(workingArea.Top, screenBounds.Top + (int)taskbarSize.Height);
                    var bottomDockPos = Math.Min(workingArea.Bottom, screenBounds.Bottom - (int)taskbarSize.Height);

                    windowPosition.Right = Math.Min(
                        placementTarget.Left + (int)windowSize.Width,
                        workingArea.Right - margin
                    );
                    windowPosition.Left = Math.Max(
                        workingArea.Left + margin,
                        windowPosition.Right - (int)windowSize.Width
                    );
                    windowPosition.Top = Math.Max(
                        topDockPos + margin,
                        placementTarget.Top - margin - (int)windowSize.Height
                    );
                    windowPosition.Bottom = Math.Min(
                        bottomDockPos - margin,
                        placementTarget.Bottom + margin + (int)windowSize.Height
                    );
                    break;
                case Helpers.Edge.Left:
                case Helpers.Edge.Right:
                    var leftDockPos = Math.Max(workingArea.Left, screenBounds.Left + (int)taskbarSize.Width);
                    var rightDockPos = Math.Min(workingArea.Right, screenBounds.Right - (int)taskbarSize.Width);

                    windowPosition.Bottom = Math.Min(
                        placementTarget.Top + (int)windowSize.Height,
                        workingArea.Bottom - margin
                    );
                    windowPosition.Top = Math.Max(
                        workingArea.Top + margin,
                        windowPosition.Bottom - (int)windowSize.Height
                    );
                    windowPosition.Left = Math.Max(
                        leftDockPos + margin,
                        placementTarget.Left - margin - (int)windowSize.Width
                    );
                    windowPosition.Right = Math.Min(
                        rightDockPos - margin,
                        placementTarget.Right + margin + (int)windowSize.Width
                    );
                    break;
            }
            return windowPosition;
        }

        private Size GetTargetWindowSize()
        {
            var windowSize = new Size(ToolbarSettings.User.PopupWidth, ToolbarSettings.User.PopupHeight);
            windowSize.Width = Math.Max(windowSize.Width, AssociatedObject.MinWidth) / _dpiScalingFactor;
            windowSize.Height = Math.Max(windowSize.Height, AssociatedObject.MinHeight) / _dpiScalingFactor;
            return windowSize;
        }

        private double GetScalingFactor()
        {
            if (PlacementTarget == null || PresentationSource.FromVisual(PlacementTarget) is not HwndSource hwndSource)
            {
                Logger.Error("Failed to get display scaling factor. This may result in incorrect window placement.");
                return 1.0;
            }

            return 96.0 / GetDpiForWindow(hwndSource.Handle);
        }

        private int GetMargin()
        {
            if (Utils.GetWindowsVersion() >= Utils.WindowsVersion.Windows11)
                return (int)(12 / GetScalingFactor());

            return 0;
        }

        [DllImport("user32")]
        static extern uint GetDpiForWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);

        [StructLayout(LayoutKind.Sequential)]
        private struct Rect
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
    }
}
